package org.mtmi.ui.controls;

import java.util.ArrayList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.mtmi.io.IControlIO;
import org.mtmi.io.controls.TrackPadIO;
import org.mtmi.tuio.listeners.DragListener;
import org.mtmi.tuio.listeners.SingleTapListener;
import org.mtmi.tuio.listeners.TUIODragEvent;
import org.mtmi.tuio.listeners.TUIOTapEvent;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.IAdaptable;
import org.mtmi.ui.controls.edits.TrackPadControlEditor;
import org.mtmi.ui.controls.listeners.ValueChangedListener;
import org.mtmi.ui.controls.listeners.ValueEvent;
import org.mtmi.ui.drawables.IDrawable;
import org.mtmi.ui.drawables.SymbolDrawable;
import org.mtmi.ui.edit.IControlEditor;
import org.mtmi.ui.layouts.LayoutData;
import org.mtmi.ui.links.CID;
import org.mtmi.ui.links.ChopBoxConnectionProvider;
import org.mtmi.ui.links.IConnectable;
import org.mtmi.ui.links.IConnectionOutlinePointProvider;
import org.mtmi.ui.scenes.COLOR;
import org.mtmi.ui.scenes.Scene;

/**
 * Track pad is a two way value generator.
 * 
 */
public class TrackPad extends Control
  implements ITouchableControl, SingleTapListener, DragListener, IConnectable
{

	public static class ValueGenerator implements IValueGenerator, IAdaptable
	{
		private ArrayList<ValueChangedListener> listeners;
		private int value, valMin, valMax;
		private TrackPad trackpad;
		
		private ValueGenerator( TrackPad t, int val, int min, int max ) {
			value=val; valMin=min; valMax=max;
			trackpad = t;
		}
		
		@Override
		public Object getAdapter( Class<?> clazz, Object arg ) {
			if( clazz == IValueGenerator.class ) {
				return this;
			}
			//delegate to trackpad
			return trackpad.getAdapter( clazz, arg );
		}
		
		public void setRange( int min, int max ) {
			valMin = min; valMax = max;
		}
		
		public void setValue( int v ) { value=v; }
		
		@Override
		public void addValueChangedListener(ValueChangedListener listener) {
			if( listeners==null ) {
				listeners = new ArrayList<ValueChangedListener>();
			}
			listeners.add( listener );
		}
		@Override
		public void removeValueChangedListener(ValueChangedListener listener) {
			if( listeners!=null ) {
				listeners.remove( listener );
			}
		}

		@Override
		public int getMaxValue() { return valMax; }

		@Override
		public int getMinValue() { return valMin; }

		@Override
		public int getValue() { return value; }

		@Override
		public float getValueF() {
			int d = valMax-valMin;
			if( d==0 ) return 0f;
			return (value-valMin)/(float)d;
		}
		
		public void setValueF( float f ) {
			f = Math.min( 1, Math.max( 0, f ) );
			value = Math.round( valMin+(valMax-valMin)*f );
			notifyListeners();
		}
		
		private void notifyListeners() {
			if( listeners!=null ) {
				ValueEvent e = new ValueEvent( trackpad, this, value );
				ValueChangedListener lis[] = listeners.toArray( new ValueChangedListener[0] );
				for( ValueChangedListener l : lis ) {
					l.valueChanged( e );
				}
			}
		}
	}
	
	private ValueGenerator h_axis, v_axis;
	
	public TrackPad(Scene scene, int _flags) {
		super(scene, _flags);
		setForeground( COLOR.grey70 );
		setBackground( COLOR.grey20 );
		h_axis = new ValueGenerator( this, 0, 0, 100 );
		v_axis = new ValueGenerator( this, 0, 0, 100 );
		
		setSize( computeSize(null) );
	}
	
	public ValueGenerator getHAxis() { return h_axis; }
	public ValueGenerator getVAxis() { return v_axis; }
	public ValueGenerator getAxis( boolean get_h_axis ) { return get_h_axis ? h_axis : v_axis; }

	@Override
	public Point computeSize( LayoutData l ) {
		return new Point( 200, 200 ); //default size
	}

	@Override
	public boolean containsTouch( TouchControlEvent e ) {
		return GraphicUtil.rectContains( location, size, e.x, e.y );
	}
	
	private int xy_track[]; //display finger track for few points (just for fun)
	private int xy_len;
	
	private void addTrackPoint( int x, int y ) {
		if( xy_len == xy_track.length ) {
			System.arraycopy(  xy_track, 2, xy_track, 0, xy_track.length-2 );
			xy_track[ xy_track.length-2 ] = x;
			xy_track[ xy_track.length-1 ] = y;
		} else {
			xy_track[ xy_len++ ] = x;
			xy_track[ xy_len++ ] = y;
		}
		redraw();
	}
	
	@Override
	public void tapDown( TUIOTapEvent event ) {
		xy_len = 0;
		if( xy_track==null ) {
			xy_track = new int[100];
		}
		addTrackPoint( event.ex, event.ey );
		notifyValueChanged( event.ex, event.ey );
	}

	@Override
	public void tapUp( TUIOTapEvent event ) {}

	@Override
	public void drag(TUIODragEvent event) {
		addTrackPoint( event.ex, event.ey );
		notifyValueChanged( event.ex, event.ey );
	}

	@Override
	public void paint( GC gc )
	{
		gc.setLineWidth( borderWidth );
		gc.setForeground( getForeground() );
		gc.setBackground( getBackground() );
		gc.fillRectangle( location.x, location.y, size.x, size.y );
		int b2 = borderWidth/2;
		gc.drawRectangle( location.x+b2, location.y+b2, size.x-borderWidth, size.y-borderWidth );
		
		if( xy_len >= 4 ) {
			gc.setForeground( COLOR.light_blue );
			gc.setAntialias( SWT.ON );
			if( xy_len==xy_track.length ) {
				gc.drawPolyline( xy_track ); 
			} else {
				int xy [] = new int[ xy_len ];
				System.arraycopy( xy_track, 0, xy, 0, xy_len );
				gc.drawPolyline( xy );
			}
		}
		
		if( getScene().isEditing() ) {
			//axis connector receiver area:
			gc.setForeground( COLOR.yellow );
			gc.setLineWidth( 3 );
			Rectangle r = getHAxisConnectorArea();
			gc.drawRectangle( r );
			GraphicUtil.expandRect( r, -10 );
			new SymbolDrawable( COLOR.dark_yellow, SymbolDrawable.ARROW_BOTTOM )
			   .paint( gc, r.x, r.y, r.width, r.height, IDrawable.NORMAL );
			
			r = getVAxisConnectorArea();
			gc.setForeground( COLOR.yellow );
			gc.setLineWidth( 3 );
			gc.drawRectangle( r );
			GraphicUtil.expandRect( r, -10 );
			new SymbolDrawable( COLOR.dark_yellow, SymbolDrawable.ARROW_LEFT )
			   .paint( gc, r.x, r.y, r.width, r.height, IDrawable.NORMAL );
		}
	}

	private void notifyValueChanged( int px, int py ) {
		float fx = (px-location.x) / (float)(size.x);
		float fy = (size.y+location.y-py) / (float)(size.y);
		h_axis.setValueF( fx );
		v_axis.setValueF( fy );
	}
	
	public Rectangle getHAxisConnectorArea() {
		int mx = location.x+size.x/2;
		return new Rectangle( mx-25, location.y+size.y-10-50, 50, 50 );
	}
	public Rectangle getVAxisConnectorArea() {
		int my = location.y+size.y/2;
		return new Rectangle( location.x+10, my-25, 50, 50 );
	}

	@Override
	public Object getAdapter( Class<?> clazz, Object arg )
	{
		if( clazz == IValueGenerator.class && arg instanceof String ) {
			String connection_id = (String)arg;
			String axis = CID.GetField( connection_id, 1 );
			if( HAXIS.equals( axis ) ) return h_axis;
			if( VAXIS.equals( axis ) ) return v_axis;
		}
		if( clazz == IConnectable.class ) {
			return this;
		}
		if( clazz == IControlEditor.class ) {
			return new TrackPadControlEditor( this );
		}
		if( clazz == IControlIO.class ) {
			return new TrackPadIO();
		}
		return super.getAdapter( clazz, arg );
	}

	private static final String HAXIS = "haxis"; //$NON-NLS-1$
	private static final String VAXIS = "vaxis"; //$NON-NLS-1$
	
	@Override
	public IConnectionOutlinePointProvider getConnectionAnchor(	String connection_id ) {
		String axis = CID.GetField( connection_id, 1 );
		if( HAXIS.equals( axis ) ) {
			return new ChopBoxConnectionProvider( getHAxisConnectorArea() );
		}
		if( VAXIS.equals( axis ) ) {
			return new ChopBoxConnectionProvider( getVAxisConnectorArea() );
		}
		return null;
	}

	@Override
	public String getConnectionAt( int x, int y ) 
	{
		if( GraphicUtil.rectContains( getHAxisConnectorArea(), x, y ) ) {
			return CID.Get( CID.VALUE_GENERATOR, HAXIS );
		}
		if( GraphicUtil.rectContains( getVAxisConnectorArea(), x, y ) ) {
			return CID.Get( CID.VALUE_GENERATOR, VAXIS );
		}
		return null;
	}
	
	@Override
	public void setBounds(int x, int y, int w, int h) {
		super.setBounds(x, y, w, h);
		//reset "snake" because it can be draw outside bounds
		xy_len = 0;
	}
}
